#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/timeb.h>

const int PLUSMINUS = 15;

int getMilliCount();
int getMilliSpan(int nTimeStart);

struct tPixelHSV
{
   int valueH;
   int valueS;
   int valueV;

   tPixelHSV()
   : valueH(0)
   , valueS(0)
   , valueV(0)
   {}
};

tPixelHSV getHSVParamsForCoordinates(const IplImage* pSrcImage, int x, int y)
{
   tPixelHSV retVal;

   if (x > pSrcImage->width  || x < 0) return retVal;
   if (y > pSrcImage->height || y < 0) return retVal;

   const int targetDepth = 8;

   IplImage* pDstImage = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 3 );
   IplImage* h_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );
   IplImage* s_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );
   IplImage* v_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );

   //  конвертируем в HSV 
   cvCvtColor( pSrcImage, pDstImage, CV_BGR2HSV );
   // разбиваем на каналы
   cvCvtPixToPlane( pDstImage, h_plane, s_plane, v_plane, 0 );

   // Getting HSV by only one pixel
   /*
   {
      uchar* ptrH = (uchar*)(h_plane->imageData + y * h_plane->widthStep);
      retVal.valueH = ptrH[x];

      uchar* ptrS = (uchar*)(s_plane->imageData + y * s_plane->widthStep);
      retVal.valueS = ptrS[x];

      uchar* ptrV = (uchar*)(v_plane->imageData + y * v_plane->widthStep);
      retVal.valueV = ptrV[x];
   }
   */

   // getting average HSV within this rectangle(square) of interest
   {
      const int ROIdimensions = 5;

      // at first calculating coordinates of rectangle. checking, if these coordinates are valid.
      // we want to get rectangle with coordinates: (x - ROIdimensions), (x + ROIdimensions), (y - ROIdimensions), (y + ROIdimensions)
      int startX = ((x - ROIdimensions) >= 0) ? (x-ROIdimensions) : 0;
      int startY = ((y - ROIdimensions) >= 0) ? (y-ROIdimensions) : 0;
      int finishX = ((x + ROIdimensions) < pSrcImage->width) ? (x + ROIdimensions) : pSrcImage->width;
      int finishY = ((y + ROIdimensions) < pSrcImage->height) ? (y + ROIdimensions) : pSrcImage->height;

      // counting average H, S, V within this rectangle
      int count = 0;
      int summH = 0;
      int summS = 0;
      int summV = 0;
      for (int y = startY; y < finishY; y++)
      {
         uchar* ptrH = (uchar*)(h_plane->imageData + y * h_plane->widthStep);
         uchar* ptrS = (uchar*)(s_plane->imageData + y * s_plane->widthStep);
         uchar* ptrV = (uchar*)(v_plane->imageData + y * v_plane->widthStep);

         for (int x = startX; x < finishX; x++)
         {
            summH += ptrH[x];
            summS += ptrS[x];
            summV += ptrV[x];
            count++;
         }
      }

      retVal.valueH = static_cast<int>(summH / count);
      retVal.valueS = static_cast<int>(summS / count);
      retVal.valueV = static_cast<int>(summV / count);
   }

   cvReleaseImage(&h_plane);
   cvReleaseImage(&s_plane);
   cvReleaseImage(&v_plane);
   cvReleaseImage(&pDstImage);

   return retVal;
}

// Not necessary to create image before calling this method
// This method creates image by itself
// Client code should release memory!!
void getTresholdedImage(const IplImage* pSrcImage, IplImage** pDstImage, 
   const int hMin, const int hMax,
   const int sMin, const int sMax,
   const int vMin, const int vMax)
{
   // TODO: check image dimensions, color depth
   // Too lazy now :)
   // image->nChannels, image->depth, image->width, image->height, image->imageSize

   const int targetDepth = 8;

   *pDstImage = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 3 );
   IplImage* h_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );
   IplImage* s_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );
   IplImage* v_plane = cvCreateImage( cvGetSize(pSrcImage), targetDepth, 1 );

   //  конвертируем в HSV 
   cvCvtColor( pSrcImage, *pDstImage, CV_BGR2HSV );
   // разбиваем на каналы
   cvCvtPixToPlane( *pDstImage, h_plane, s_plane, v_plane, 0 );

   //cvReleaseImage(&pDstImage);

   for (int y=0; y<h_plane->height; y++)
   {
      uchar* ptrH = (uchar*)(h_plane->imageData + y * h_plane->widthStep);
      uchar* ptrS = (uchar*)(s_plane->imageData + y * s_plane->widthStep);
      uchar* ptrV = (uchar*)(v_plane->imageData + y * v_plane->widthStep);

      for (int x=0; x<h_plane->width; x++)
      {
         if (ptrH[x] >= hMin && ptrH[x] <= hMax)
         {
            ptrH[x] = 255;
         } else
         {
            ptrH[x] = 0;
         }
         if (ptrS[x] >= hMin && ptrS[x] <= hMax)
         {
            ptrS[x] = 255;
         } else
         {
            ptrS[x] = 0;
         }
         if (ptrV[x] >= hMin && ptrV[x] <= hMax)
         {
            ptrV[x] = 255;
         } else
         {
            ptrV[x] = 0;
         }
      }
   }

   //cvSaveImage("ImageOutH.jpg", h_plane);
   //cvSaveImage("ImageOutS.jpg", s_plane);
   //cvSaveImage("ImageOutV.jpg", v_plane);

   cvReleaseImage(&h_plane);
   cvReleaseImage(&s_plane);
   cvReleaseImage(&v_plane);

   return;
}

int main(int argc, char* argv[])
{
   IplImage* image = 0;
   //IplImage* src = 0;
   
   //IplImage* channelR = 0;
   //IplImage* channelG = 0;
   //IplImage* channelB = 0;
   
   // имя картинки задаётся первым параметром
   const char* filename = argc == 2 ? argv[1] : "Image0.jpg";
   // получаем картинку
   image = cvLoadImage(filename,1);

   for (int i =0; i<100; i++)
   {
      int currentTime = getMilliCount();

      tPixelHSV pixel = getHSVParamsForCoordinates(image, i, i);
      IplImage* pThresholdedImage = 0;
      
      getTresholdedImage(image, &pThresholdedImage, 
         pixel.valueH - PLUSMINUS, pixel.valueH + PLUSMINUS, 
         pixel.valueS - PLUSMINUS, pixel.valueS + PLUSMINUS, 
         pixel.valueV - PLUSMINUS, pixel.valueV + PLUSMINUS);

      printf("pThresholdedImage = %p\n", pThresholdedImage);
      cvReleaseImage(&pThresholdedImage);
      printf("Image processing took %d ms\n", getMilliSpan(currentTime));
   }

   // клонируем картинку 
   //src = cvCloneImage(image);

   printf("[i] image: %s\n", filename);
   assert( image != 0 );

   // окно для отображения картинки
   //cvNamedWindow("original",CV_WINDOW_AUTOSIZE);

   // показываем картинку
   //cvShowImage("original",image);

   // выводим в консоль информацию о картинке
   printf( "[i] channels:  %d\n",        image->nChannels );
   printf( "[i] pixel depth: %d bits\n",   image->depth );
   printf( "[i] width:       %d pixels\n", image->width );
   printf( "[i] height:      %d pixels\n", image->height );
   printf( "[i] image size:  %d bytes\n",  image->imageSize );
   printf( "[i] width step:  %d bytes\n",  image->widthStep );

   // Creating 3 image-channels
   /*
   channelR = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U, 1);
   channelG = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U, 1);
   channelB = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U, 1);
   */
   
   //cvSplit(src, channelB, channelG, channelR, 0);



   // создаём изображения
   IplImage* hsv = cvCreateImage( cvGetSize(image), 8, 3 );
   IplImage* h_plane = cvCreateImage( cvGetSize(image), 8, 1 );
   IplImage* s_plane = cvCreateImage( cvGetSize(image), 8, 1 );
   IplImage* v_plane = cvCreateImage( cvGetSize(image), 8, 1 );
   
   //  конвертируем в HSV 
   cvCvtColor( image, hsv, CV_BGR2HSV );
   // разбиваем на каналы
   cvCvtPixToPlane( hsv, h_plane, s_plane, v_plane, 0 );
   
   // These constans are dummy. Should be experimentaly found
   /*
   int channelRMin = 100;
   int channelRMax = 128;
   int channelGMin = 100;
   int channelGMax = 128;
   int channelBMin = 100;
   int channelBMax = 128;
   */
   
   /*
   cvThreshold(channelR, channelR, channelRMin, 255, CV_THRESH_BINARY);
   cvThreshold(channelG, channelG, channelGMin, 255, CV_THRESH_BINARY);
   cvThreshold(channelB, channelB, channelBMin, 255, CV_THRESH_BINARY);
   */

   //cvSaveImage("ImageOutH.jpg", h_plane);
   //cvSaveImage("ImageOutS.jpg", s_plane);
   //cvSaveImage("ImageOutV.jpg", v_plane);

   // ждём нажатия клавиши
   cvWaitKey(0);

   // освобождаем ресурсы
   cvReleaseImage(&image);
   //cvReleaseImage(&src);
   cvReleaseImage(&h_plane);
   cvReleaseImage(&s_plane);
   cvReleaseImage(&v_plane);
   // удаляем окно
   //cvDestroyWindow("original");
   return 0;
}

int getMilliCount(){
    timeb tb;
    ftime(&tb);
    int nCount = tb.millitm + (tb.time & 0xfffff) * 1000;
    return nCount;
}
int getMilliSpan(int nTimeStart){
    int nSpan = getMilliCount() - nTimeStart;
    if(nSpan < 0)
        nSpan += 0x100000 * 1000;
    return nSpan;
}